#include "stdafx.h"
#include "game.h"
#include <math.h>
#include <stdio.h>
#include "texture.h"
#include <time.h>

GLuint textures[TEX_COUNT];
/***************************************************************
++++++++++++++++++++++ OPENGL HELPERS ++++++++++++++++++++++++++
***************************************************************/
void Game::init_textures()
{
	glEnable(GL_TEXTURE_2D); // This Enable the Texture mapping
	textures[BLANK] = texture::bitmap("blank.bmp");
	textures[BOMB_DOT] = texture::bitmap("bomb.bmp");
	textures[RED_DOT] = texture::bitmap("red_dot.bmp");
	textures[BLUE_DOT] = texture::bitmap("blue_dot.bmp");
	textures[GREEN_DOT] = texture::bitmap("green_dot.bmp");
	textures[PURPLE_DOT] = texture::bitmap("purple_dot.bmp");
	textures[YELLOW_DOT] = texture::bitmap("yellow_dot.bmp");
	textures[GOAL] = texture::bitmap("end.bmp");
	textures[BACKDROP1] = texture::bitmap("backdrop1.bmp");
	textures[BACKDROP2] = texture::bitmap("backdrop2.bmp");
	textures[BACKDROP3] = texture::bitmap("backdrop3.bmp");
	textures[BACKDROP4] = texture::bitmap("backdrop4.bmp");
}

void Game::set_color(games::Type t, float a)
{
	switch (t){
		case RED: glColor4f(1.0f,0.0f,0.0f,a); return;
		case BLUE: glColor4f(0.0f,0.0f,1.0f,a); return;
		case GREEN: glColor4f(0.0f,1.0f,0.0f,a); return;
		case PURPLE: glColor4f(1.0f,0.0f,1.0f,a); return;
		case YELLOW: glColor4f(1.0f,1.0f,0.0f,a); return;
		case TO_YELLOW: case TO_RED: case TO_BLUE: case TO_GREEN: case TO_PURPLE:  
		case BOMB: case GRAY: case END: glColor4f(1.0f,1.0f,1.0f,a); return;
	}
}

void Game::set_texture(games::Type t)
{
	switch (t){
		case TO_RED: glBindTexture(GL_TEXTURE_2D, textures[RED_DOT]); return; 
		case TO_BLUE: glBindTexture(GL_TEXTURE_2D, textures[BLUE_DOT]); return; 
		case TO_GREEN: glBindTexture(GL_TEXTURE_2D, textures[GREEN_DOT]); return; 
		case TO_PURPLE: glBindTexture(GL_TEXTURE_2D, textures[PURPLE_DOT]); return; 
		case TO_YELLOW: glBindTexture(GL_TEXTURE_2D, textures[YELLOW_DOT]); return;
		case BOMB: glBindTexture(GL_TEXTURE_2D, textures[BOMB_DOT]); return;
		case END: glBindTexture(GL_TEXTURE_2D, textures[GOAL]); return;
		default: glBindTexture(GL_TEXTURE_2D, textures[BLANK]); return;
	}

}

/***************************************************************
++++++++++++++++++++++ OBJECT CREATION +++++++++++++++++++++++++
***************************************************************/
void Game::paint_backdrop()
{
	glBindTexture(GL_TEXTURE_2D, textures[backdrop]);
	float z = (float)WIDTH/(max(COLS,ROWS));
	glBegin(GL_QUADS);
	{
		glColor4f(1.0f,1.0f,1.0f,1.0f);
		glNormal3f(0.0f,0.0f,1.0f); //top
		glTexCoord2f(0.0f, 0.0f); glVertex3f(0,0,-z);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(WIDTH,0,-z);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(WIDTH,HEIGHT,-z);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(0,HEIGHT,-z);
	}
	glEnd();
}
/* Initializes the playing grid as a series of white points */
int Game::init_grid()
{
	glNewList(GRID, GL_COMPILE);
	glBegin(GL_POINTS);
	{
		glColor4f(1.0f,1.0f,1.0f,1.0f);
		for (float i = 0.0f; i <= (float)WIDTH; i += (float)WIDTH/((float)COLS))
			for(float j = 0.0f; j <= (float)HEIGHT; j += (float)HEIGHT/((float)ROWS))
				glVertex2f(i,j);
	}
	glEnd();
	glEndList();
	return GRID;
}
/* Initializes the generic block as an extruded cube 
that fits within a grid space with some predefined padding */
int Game::init_block()
{
	float x = (float)(((float)WIDTH/(float)COLS)/2 - PADDING);
	float y = (float)(((float)HEIGHT/(float)ROWS)/2 - PADDING);
	float z = y;

	glNewList(BLOCK, GL_COMPILE);
	glBegin(GL_QUADS);
	{
		glNormal3f(0.0f,0.0f,-1.0f); //bottom
		glVertex3f(-x,-y,-z); glVertex3f(-x,y,-z); glVertex3f(x,y,-z); glVertex3f(x,-y,-z);
		glNormal3f(0.0f,1.0f,0.0f); //back
		glVertex3f(-x,y,-z); glVertex3f(-x,y,z); glVertex3f(x,y,z); glVertex3f(x,y,-z);
		glNormal3f(-1.0f,0.0f,0.0f); //left
		glVertex3f(x,-y,-z); glVertex3f(x,y,-z); glVertex3f(x,y,z);	glVertex3f(x,-y,z);
		glNormal3f(1.0f,0.0f,0.0f); //right
		glVertex3f(-x,-y,-z); glVertex3f(-x,y,-z); glVertex3f(-x,y,z); glVertex3f(-x,-y,z);
		glNormal3f(0.0f,-1.0f,0.0f); //front
		glVertex3f(-x,-y,-z); glVertex3f(x,-y,-z); glVertex3f(x,-y,z); glVertex3f(-x,-y,z);
		glNormal3f(0.0f,0.0f,1.0f); //top
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-x,-y,z);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(x,-y,z);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(x,y,z);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-x,y,z);

	}
	glEnd();
	glEndList();
	return BLOCK;

}
/* Initializes the ball as a solid sphere */
int Game::init_ball()
{
	glBindTexture(GL_TEXTURE_2D, textures[BLANK]);
	ball = gluNewQuadric();
	gluQuadricDrawStyle(ball, GLU_FILL);
	gluQuadricTexture(ball, GL_TRUE);
	gluQuadricOrientation(ball, GLU_OUTSIDE);
	gluQuadricNormals(ball, GLU_SMOOTH);
	glNewList(BALL, GL_COMPILE);
	gluSphere(ball,RADIUS,SLICES,STACKS);
	glEndList();
	return BALL;
}

void Game::update_pos()
{
	// determine the current cell the ball is in, update if different
	// current cell based on balls position relative to the grid
	float w = (float)BLOCK_WIDTH;
	float h = (float)BLOCK_HEIGHT;

	if (time(NULL) - last_hit > HIT_TIMEOUT)
		end_game();

	// this would be floor if our cell was zero based
	// local copy for locality of memory
	cur_cell[0] = (float)floor(center[0] / w);
	cur_cell[1] = (float)floor(center[1] / h);

	bool result = false;
	if (cur_cell[0] - 1 >= 0)
		result |= grid[(int)cur_cell[0] - 1][(int)cur_cell[1]] == NULL;
	if (cur_cell[0] + 1 < COLS)
		result |= grid[(int)cur_cell[0] + 1][(int)cur_cell[1]] == NULL;
	if (cur_cell[1] - 1 >= 0)
		result |= grid[(int)cur_cell[0]][(int)cur_cell[1] - 1] == NULL;
	if (cur_cell[1] + 1 < ROWS)
		result |= grid[(int)cur_cell[0]][(int)cur_cell[1] + 1] == NULL;
	playing &= result;
}

/***************************************************************
+++++++++++++++++++ COLLISION DETECTION ++++++++++++++++++++++++
***************************************************************/
void Game::handle_collisions()
{
	// determine the current cell the ball is in, update if different
	// current cell based on balls position relative to the grid
	float w = (float)BLOCK_WIDTH;
	float h = (float)BLOCK_HEIGHT;

	// this would be floor if our cell was zero based
	// local copy for locality of memory
	int cell_x = (int)(cur_cell[0]);
	int cell_y = (int)(cur_cell[1]);

	// truncate cell to ensure it is NOT out of grid boundaries
	// this may not be a case, verify, and if not, then this
	// can be removed
	//cell_x = min(max(cell_x, 1), COLS);
	//cell_y = min(max(cell_y, 1), ROWS);

	// need to update cell information if it's different
	//if(cell_x != cur_cell[0] || cell_y != cur_cell[1])
	{
		// determine quadrant of current cell
		// this will be used to determine which neighboring blocks need 
		// to be checked in the case that the sphere is inbetween cells
		cell_quad[0] = (center[0] < w * ((cell_x) + 0.5)) ? ((cell_x > 0) ? -1 : 0) : ((cell_x < COLS -1)? 1 : 0);
		cell_quad[1] = (center[1] < h * ((cell_y) + 0.5)) ? ((cell_y > 0) ? -1 : 0) : ((cell_y < ROWS - 1)? 1 : 0);

	}

	// is there anything in my cell or the 3 neighbors to my quadrant of the cell?
	// if so, we need to check and see if we are colliding with that object(s)
	// otherwise, move on

	if(grid[cell_x][cell_y] != NULL)
		if(is_collision(cell_x, cell_y))
			return;

	if(cell_quad[0] != 0 && grid[cell_x + cell_quad[0]][cell_y] != NULL)
		if(is_collision(cell_x + cell_quad[0], cell_y))
			return;

	if(cell_quad[1] != 0 && grid[cell_x][cell_y + cell_quad[1]] != NULL)
		if(is_collision(cell_x, cell_y + cell_quad[1]))
			return;
	
	if(cell_quad[0] != 0 && cell_quad[1] != 0 && grid[cell_x + cell_quad[0]][cell_y + cell_quad[1]] != NULL)
		if(is_collision(cell_x + cell_quad[0], cell_y + cell_quad[1]))
			return;
}

float * Game::is_collision(int cell_x, int cell_y)
{
	float dist[2];
	float normal[2];

	// block center
	int block_x = (int)grid[cell_x][cell_y]->center[0];
	int block_y = (int)grid[cell_x][cell_y]->center[1];

	// determine which voronoi region the ball is in (in relation to the box)
	// -1 = left or bottom, 0 = middle, 1 = right or top
	float horiz_vr = 0.0f;
	float vert_vr  = 0.0f;

	// horizontal region
	if(center[0] < (block_x - HALF_BLOCK_WIDTH))
		horiz_vr = -1.0f;
	else if(center[0] > (block_x + HALF_BLOCK_WIDTH))
		horiz_vr = 1.0f;

	// vertical region
	if(center[1] < (block_y - HALF_BLOCK_HEIGHT))
		vert_vr = -1.0f;
	else if(center[1] > (block_y + HALF_BLOCK_HEIGHT))
		vert_vr = 1.0f;


	//corner case - in the corner voronoi regions
	//there can be no collision without hitting the corner
	//thus we ONLY need to check against the corners
	if(horiz_vr != 0 && vert_vr != 0)
	{
		//get closest block corner
		float corner_x = block_x + horiz_vr * HALF_BLOCK_WIDTH;
		float corner_y = block_y + vert_vr * HALF_BLOCK_HEIGHT;
		
		//dist from center to corner
		dist[0] = abs(center[0] - corner_x)  + PADDING;
		dist[1] = abs(center[1] - corner_y)  + PADDING;
		float mag = sqrt(pow(dist[0], 2) + pow(dist[1], 2));

		if(mag > RADIUS)
			return NULL;

		// collision normal is dist normalized
		normal[0] = dist[0] / mag * horiz_vr;
		normal[1] = dist[1] / mag * vert_vr;

	} else {
		// must be an edge case
		// get dist to block
		dist[0] = abs(center[0] - block_x) - (HALF_BLOCK_WIDTH + RADIUS) + PADDING;

		// horizontal gap?
		if(dist[0] > 0.0f)
			return NULL;

		dist[1] = abs(center[1] - block_y) - (HALF_BLOCK_HEIGHT + RADIUS) + PADDING;

		// vertical gap?
		if(dist[1] > 0.0f)
			return NULL;

		// choose axis of least overlap
		// to be collision axis / normal
		if(dist[0] > dist[1]) 
		{
			dist[1] = 0.0f;
			normal[0] = horiz_vr;
			normal[1] = 0.0f;
		} else {
			dist[0] = 0.0f;
			normal[0] = 0.0f;
			normal[1] = vert_vr;
		}
	}

	// if we're here... then there was a collision
	// and dist contains collision normal


	// adjust by amount of overlap in direction of collision normal
	// it seems to work better without this.... corner cases
	// gave some pretty noticable jumps
	center[0] += abs(dist[0]) * horiz_vr;
	center[1] += abs(dist[1]) * vert_vr;

	// adjust direction about normal
	// I' = I - 2 * dot(N,I) * N
	float n_dot_v = normal[0] * vector[0] + normal[1] * vector[1];

	float dif[2];
	dif[0] = 2.0f * n_dot_v * normal[0];
	dif[1] = 2.0f * n_dot_v * normal[1];

	vector[0] = vector[0] - dif[0];
	vector[1] = vector[1] - dif[1];

	if (dist)
		handle_reaction(cell_x,cell_y);

	return dist;
}

void Game::handle_bomb(int cell_x, int cell_y)
{
	//removes the bomb
	unset_block(cell_x,cell_y);

	//tests each of the neighbors, and deletes them
	for (int i = cell_x - 1; i < cell_x + 2; i++)
	{
		for (int j = cell_y - 1; j < cell_y + 2; j++)
		{
			if (i >= 0 && i < COLS && j >= 0 && j < ROWS)
			{
				if (grid[i][j] != NULL)
				{
					switch (grid[i][j]->type)
					{
						case END: break;
						case BOMB: handle_bomb(i,j); break;
						case GRAY: break; 
						default: unset_block(i,j); break;
 					}
				}
			}
		}
	}
}

void Game::handle_reaction(int cell_x, int cell_y)
{
	//vector[axis] /= 2.0f;
	//whenever we break out of the switch statement, the block dissapears
	//whenever we return straight out of the switch, the block will not be removed
	switch (grid[cell_x][cell_y]->type)
	{
		case TO_RED: type = RED; if (mode = OUTBLAST) return; else break;
		case TO_BLUE: type = BLUE; if (mode = OUTBLAST) return; else break;
		case TO_GREEN: type = GREEN; if (mode = OUTBLAST) return; else break;
		case TO_PURPLE: type = PURPLE; if (mode = OUTBLAST) return; else break;
		case TO_YELLOW: type = YELLOW; if (mode = OUTBLAST) return; else break;
		case BOMB: handle_bomb(cell_x,cell_y); return; //block is removed in the handle_bomb function
		case END: end_game(); return;
		default: if (grid[cell_x][cell_y]->type == type) break; else return;
	}

	unset_block(cell_x,cell_y);
	return;
	
}

void Game::handle_boundaries()
{
	if (center[0] + RADIUS > boundary_x_pos) //outside of positive x boundary
	{
		center[0] = boundary_x_pos - RADIUS; //moves the ball to butt up against the wall
		vector[0] = -abs(vector[0]); //ensures velocity away from the wall
	}
	else if (center[0] - RADIUS < boundary_x_neg) //outside of negative x boundary
	{
		center[0] = RADIUS; //moves the ball to butt up against the wall
		vector[0] = abs(vector[0]); //ensures velocity away from the wall
	}
	if (center[1] + RADIUS > boundary_y_pos) //outside of positive y boundary
	{
		center[1] = boundary_y_pos - RADIUS; //moves the ball to butt up against the wall
		vector[1] = -abs(vector[1]); //ensures velocity away from the wall
	}
	else if (center[1] - RADIUS < boundary_y_neg) //outside of negative y boundary
	{
		center[1] = RADIUS; //moves the ball to butt up against the wall
		vector[1] = abs(vector[1]); //ensures velocity away from the wall
	}
}

/***************************************************************
++++++++++++++++++++++ BALL MOVEMENT +++++++++++++++++++++++++++
***************************************************************/
/*
this method defines the behavior of the ball irrespective of the environment.
Once the center and vector have been modified by this method 
the environment is considered and collisions detected.
theta is the angle of tilt of the device.
*/
void Game::handle_gravity(float theta)
{
	//applies gravity along the free axis based on the angle of tilt (theta)
	vector[axis] += G*theta;
	//handles corner cases
	//when the ball bounces off a corner, the vector can be adjusted, spoiling the constant speed in the off-axis direction
	//the following code ensures that the ball re-approaches the constant speed
	if (abs(vector[!axis]) > (float)V)
		vector[!axis] /= (float)V_STEP;
	else if (abs(vector[!axis]) < (float)V)
		vector[!axis] *= (float)V_STEP;
	

	//applies friction along the free axis
	if (vector[axis] < 0 && abs(vector[axis]) > F)
	{
		vector[axis] += F; //friction
		if (vector[axis] < -T) 
			vector[axis] = -T; //terminal velocity
	}
	else if (vector[axis] > 0  && abs(vector[axis]) > F)
	{
		vector[axis] -= F; //friction
		if (vector[axis] > T)
			vector[axis] = T; //terminal velocity
	}
	else
		vector[axis] = 0.0;

	//adjusts the position of the ball using the velocity vector
	center[0] += vector[0];
	center[1] += vector[1];
}

//sets up a new game
void Game::init_game(int game_id)
{
	/* initialize random seed: */
	srand ( time(NULL) );
	last_hit = time(NULL);
	types = 1;
	playing = true;
	axis = 0; //the ball moves freely in the x direction
	vector[0] = 0.0f; //initial velocity of the ball
	vector[1] = 0.0f; //initial velocity of the ball

	vector[!axis] = V; //sets the fixed velocity along the constrained axis

	boundary_y_neg = 0.0f; //sets boundary
	boundary_x_neg = 0.0f; //sets boundary
	boundary_y_pos = HEIGHT; //sets boundary
	boundary_x_pos = WIDTH; //sets boundary

	int count, back; //number of Boxes in a game (set when passed into game builder method)
	float * c = new float[2]; //container for start position of ball (set by game builder method)
	games::Box * boxes = games::get_game(game_id,count,&c,type,back,mode); //call to game builder method
	set_grid(boxes,count,&c,back); //sets up the grid from the loaded game
	delete c; //cleans up the start position container
}

void Game::end_game()
{
	playing = false;
}

/*****************************************************
++++++++++++++++++ GRID MANAGEMENT +++++++++++++++++++
*****************************************************/
void Game::set_block(games::Box box)
{
	if (box.col - 1 < COLS && box.col - 1 >= 0 && box.row - 1 < ROWS && box.row - 1 >= 0)
	{
		float x = BLOCK_WIDTH/2.0f  - PADDING; //determines the distance from the block's center in x
		float y = BLOCK_HEIGHT/2.0f - PADDING; //determines the distance from the block's center in y
		games::Block * block = new Block(); //creates a new block

		block->center[0] = BLOCK_WIDTH  * (0.5f + box.col - 1); //center of the block in x
		block->center[1] = BLOCK_HEIGHT * (0.5f + box.row - 1); //center of the block in y
		block->border_x_neg = block->center[0] - x; //left side of the block
		block->border_x_pos = block->center[1] + x; //right side of the block
		block->border_y_neg = block->center[0] - y; //bottom of the block
		block->border_y_pos = block->center[1] + y; //top of the block
		block->type = box.type; //type of the block

		if (grid[box.col - 1][box.row - 1] != NULL) 
			delete grid[box.col - 1][box.row - 1]; //replaces the previous block
		grid[box.col - 1][box.row - 1] = block; //adds the block to the grid
	}
}

void Game::update_score()
{
	score ++;
	
	if (types < 6)
	{
		if (score > (types) * BLOCKS_PER_LEVEL)
		{
			types++;
			//backdrop++;
		}
	}
	
}

void Game::unset_block(int col,int row)
{
	if (grid[col][row] != NULL)
	{					
		delete grid[col][row];
		grid[col][row] = NULL;
		update_score();
		last_hit = time(NULL);
	}
}

//converts a list of Boxes into Blocks on the grid
void Game::set_grid(games::Box * level,int count, float ** start, int back)
{
	//clear_grid();
	backdrop = BACKDROP1 + back;
	center[0] = (float)((float)WIDTH * (*start)[0]); //sets the ball's starting point in x
	center[1] = (float)((float)HEIGHT * (*start)[1]); //sets the ball's starting point in y

	/* Iterates through the list of  boxes */
	for (int i = 0; i < count; i++)
	{
		games::Box box = level[i]; //considers one block at a time
		set_block(box);
	}
}

void Game::clear_grid()
{
	for (int x = 0; x < COLS; x++)
	{
		for (int y = 0; y < ROWS; y++)
		{
			if (grid[x][y] != NULL)
			{
				delete grid[x][y];
				grid[x][y] = NULL;
			}
		}
	}
}

games::Type colors[] = {YELLOW,BLUE,GREEN,RED,PURPLE,GRAY};
games::Type rand_type(int i)
{
	int r = (rand() % i);
	return colors[r];
}

games::Type to_colors[] = {TO_YELLOW,TO_BLUE,TO_GREEN,TO_RED,TO_PURPLE,GRAY};
games::Type rand_to_type(int i)
{
	int r = (rand() % i);
	return to_colors[r];
}

void Game::rand_populate_grid()
{
	rand_populate_span(2,2,ROWS-2,COLS-2,rand_type(types),BLOCK_RATE);
	rand_populate_span(2,2,ROWS-2,COLS-2,BOMB,BOMB_RATE);
	rand_populate_span(1,2,1,COLS-2,rand_to_type(types),PAINT_RATE,true);
	rand_populate_span(ROWS,2,1,COLS-2,rand_to_type(types),PAINT_RATE,true);
	rand_populate_span(2,1,ROWS-2,1,rand_to_type(types),PAINT_RATE,true);
	rand_populate_span(2,COLS,ROWS-2,1,rand_to_type(types),PAINT_RATE,true);

}

void Game::rand_populate_span(int row_start,int col_start,int row_span,int col_span,Type type,int odds, bool replace)
{
	int random = rand() % 1000;
	if (random < odds)
	{
		int col = col_start + (rand() % col_span);
		int row = row_start + (rand() % row_span);

		if ((replace || grid[col-1][row-1] == NULL) && (cur_cell[0] != col-1 || cur_cell[1] != row-1))
		{
			games::Box box;
			box.col = col;
			box.row = row;
			box.type = type;
			set_block(box);
		}
	}	
}

Game::~Game()
{
	clear_grid();
}